/**
 *Copyright 2013 by dragon.
 *
 *File name: PeerMessage.java
 *Author:      dragon
 *Email:       fufulove2012@gmail.com
 *Blog:        http://blog.csdn.net/xidomlove
 *Version:     1.0.0
 *Date:        2013-10-8 下午3:47:52
 *Description: 参考：http://blog.sina.com.cn/s/blog_4ab2ba570100y7fs.html
 */
package com.dragon.jaxel.bittorrent;

import java.nio.ByteBuffer;

/**
 * @author dragon8
 * 
 */
public class PeerMessage {

	final static int MESSAGE_KEEP_ALIVE = -1;
	final static int MESSAGE_CHOKE = 0;
	final static int MESSAGE_UNCHOKE = 1;
	final static int MESSAGE_INTERESTED = 2;
	final static int MESSAGE_NOT_INTERESTED = 3;
	final static int MESSAGE_HAVE = 4;
	final static int MESSAGE_BIT_FIELD = 5;
	final static int MESSAGE_REQUEST = 6;
	final static int MESSAGE_PIECE = 7;
	final static int MESSAGE_CANCEL = 8;
	final static int MESSAGE_PORT = 9;

	final static int OFFSET_LEN = 0;
	final static int OFFSET_MESSAGE_ID = 4;
	final static int OFFSET_PAYLOAD = 5;

	/**
	 * 
	 */
	private PeerMessage() {
	}

	/**
	 * bitfield消息：(len=0001+X)(id=5)(bitfield)
	 * bitfield消息的长度不固定，其中X是bitfield(即位图)
	 * 的长度(位数，不是字节数)。当客户端与peer交换握手消息之后，就交换位图。位图中，每个piece占一位
	 * ，若该位的值为1，则表明已经拥有该piece；为0则表明该piece尚未下载
	 * 。具体而言，假定某共享文件共拥有801个piece，则位图为101个字节
	 * ，位图的第一个字节的最高位指明第一个piece是否拥有，位图的第一个字节的第二高位指明第二个piece是否拥有
	 * ，依此类推。对于第801个piece，需要单独一个字节，该字节的最高位指明第801个piece是否已被下载，其余的7位放弃不予使用。
	 * 
	 * @param buffer
	 * @param bitSet
	 */
	static ByteBuffer wrapBitFieldMessage(BitSet bitSet) {
		ByteBuffer buffer = ByteBuffer
				.allocate(bitSet.getBits().length + 1 + 4);
		buffer.putInt(1 + bitSet.getBitCount());
		buffer.put((byte) MESSAGE_BIT_FIELD);
		buffer.put(bitSet.getBits());
		buffer.flip();
		return buffer;
	}

	/**
	 * keep_alive消息的长度固定，为4字节，它没有消息编号和负载。如果一段时间内客户端与peer没有交换任何消息，则与这个peer的连接将被关闭
	 * 。keep_alive消息用于维持这个连接，通常如果2分钟内没有向peer发送任何消息，则发送一个keep_alive消息。
	 * 
	 * @param buffer
	 */
	static ByteBuffer wrapKeepAlive() {
		ByteBuffer buffer = ByteBuffer.allocate(32);
		buffer.putInt(0);
		buffer.flip();
		return buffer;
	}

	/**
	 * choke消息：(len=0001)(id=0) choke消息的长度固定，为5字节，消息长度占4个字节，消息编号占1个字节，没有负载。
	 * 
	 * @param buffer
	 * @return
	 */
	static ByteBuffer wrapChoke() {
		ByteBuffer buffer = ByteBuffer.allocate(32);
		buffer.putInt(1);
		buffer.put((byte) MESSAGE_CHOKE);
		buffer.flip();
		return buffer;
	}

	/**
	 * unchoke消息：(len=0001)(id=1)
	 * unchoke消息的长度固定，为5字节，消息长度占4个字节，消息编号占1个字节，没有负载。客户端每隔一定的时间
	 * ，通常为10秒，计算一次各个peer的下载速度
	 * ，如果某peer被解除阻塞，则发送unchoke消息。如果某个peer原先是解除阻塞的，而此次被阻塞，则发送choke消息。
	 * 
	 * @param buffer
	 * @return
	 */
	static ByteBuffer wrapUnChoke() {
		ByteBuffer buffer = ByteBuffer.allocate(32);
		buffer.putInt(1);
		buffer.put((byte) MESSAGE_UNCHOKE);
		buffer.flip();
		return buffer;
	}

	/**
	 * interested消息：(len=0001)(id=2)
	 * interested消息的长度固定，为5字节，消息长度占4个字节，消息编号占1个字节，没有负载
	 * 。当客户端收到某peer的have消息时，如果发现peer拥有了客户端没有的piece
	 * ，则发送interested消息告知该peer，客户端对它感兴趣。
	 * 
	 * @param buffer
	 */
	static ByteBuffer wrapInterested() {
		ByteBuffer buffer = ByteBuffer.allocate(32);
		buffer.putInt(1);
		buffer.put((byte) MESSAGE_INTERESTED);
		buffer.flip();
		return buffer;
	}

	/**
	 * not interested消息：(len=0001)(id=3) not
	 * interested消息的长度固定，为5字节，消息长度占4个字节，消息编号占1个字节
	 * ，没有负载。当客户端下载了某个piece，如果发现客户端拥有了这个piece后，某个peer拥有的所有piece，客户端都拥有，则发送not
	 * interested消息给该peer。
	 * 
	 * @param buffer
	 */
	static ByteBuffer wrapNotInterested() {
		ByteBuffer buffer = ByteBuffer.allocate(32);
		buffer.putInt(1);
		buffer.put((byte) MESSAGE_NOT_INTERESTED);
		buffer.flip();
		return buffer;
	}

	/**
	 * have消息：(len=0005)(id=4)(piece index)
	 * have消息的长度固定，为9字节，消息长度占4个字节，消息编号占1个字节，
	 * 负载为4个字节。负载为一个整数，指明下标为index的piece，peer已经拥有
	 * 。每当客户端下载了一个piece，即将该piece的下标作为have消息的负载构造have消息，并把该消息发送给所有与客户端建立连接的peer。
	 * 
	 * @param buffer
	 * @param index
	 *            拥有的piece索引
	 */
	static ByteBuffer wrapHave(int index) {
		ByteBuffer buffer = ByteBuffer.allocate(32);
		buffer.putInt(5);
		buffer.put((byte) MESSAGE_HAVE);
		buffer.putInt(index);
		buffer.flip();
		return buffer;
	}

	/**
	 * request消息：(len=0013)(id=6)(index)(begin)(length)
	 * request消息的长度固定，为17个字节，index是piece的索引
	 * ，begin是piece内的偏移，length是请求peer发送的数据的长度
	 * 。当客户端收到某个peer发来的unchoke消息后，即构造request消息
	 * ，向该peer发送数据请求。前面提到，peer之间交换数据是以slice
	 * （长度为16KB的块）为单位的，因此request消息中length的值一般为16K
	 * 。对于一个256KB的piece，客户端分16次下载，每次下载一个16K的slice。
	 * 
	 * @param buffer
	 * @param index
	 *            Request的piece索引
	 * @param begin
	 *            piece起始偏移
	 * @param length
	 *            piece的长度
	 */
	static ByteBuffer wrapRequest(int index, int begin, int length) {
		ByteBuffer buffer = ByteBuffer.allocate(32);
		buffer.putInt(13);
		buffer.put((byte) MESSAGE_REQUEST);
		buffer.putInt(index);
		buffer.putInt(begin);
		buffer.putInt(length);
		buffer.flip();
		return buffer;
	}

	/**
	 * 生成一个Piece消息 piece消息：(len=0009+X)(id=7)(index)(begin)(block)
	 * piece消息是另外一个长度不固定的消息，长度前缀中的9是id、index、begin的
	 * 长度总和，index和begin固定为4字节，X为block的长度，一般为16K。因此对于 piece消息，长度前缀加上id通常为00 00 40
	 * 09 07。当客户端收到某个peer的 request消息后，如果判定当前未将该peer阻塞，且peer请求的slice，客户端已
	 * 经下载，则发送piece消息将文件数据上传给该peer。
	 * 
	 * @param buffer
	 * @param index
	 * @param begin
	 * @param block
	 *            文件块，通常长度16K
	 * @return
	 */
	static ByteBuffer wrapPiece(int index, int begin, int blockLength) {
		ByteBuffer buffer = ByteBuffer.allocate(32);
		buffer.putInt(9 + blockLength);
		buffer.put((byte) MESSAGE_PIECE);
		buffer.putInt(index);
		buffer.putInt(begin);
		buffer.flip();
		return buffer;
	}

	/**
	 * cancel消息：(len=0013)(id<=8)(index)(begin)(length)
	 * cancel消息的长度固定，为17个字节，len、
	 * index、begin、length都占4字节。它与request消息对应，作用刚好相反，用于取消对某个slice的数据请求
	 * 。如果客户端发现，某个piece中的slice
	 * ，客户端已经下载，而客户端又向其他peer发送了对该slice的请求，则向该peer发送cancel消息
	 * ，以取消对该slice的请求。事实上，如果算法设计合理，基本不用发送cancel消息，只在某些特殊的情况下才需要发送cancel消息。
	 * 
	 * @param buffer
	 * @param index
	 * @param begin
	 * @param length
	 */
	static ByteBuffer wrapCancel(int index, int begin, int length) {
		ByteBuffer buffer = ByteBuffer.allocate(32);
		buffer.putInt(13);
		buffer.put((byte) MESSAGE_CANCEL);
		buffer.putInt(index);
		buffer.putInt(begin);
		buffer.putInt(length);
		buffer.flip();
		return buffer;
	}
}
